import xmlFormat from 'xml-formatter';
/**
 * @description 正常情况下处理json2xml转的内容,其他xml格式可能无法解析为正确的结果
 * @example
 *  xml2json(`
 *            <application>
 *              <array type="array">
 *                <array-item type="number">1</value>
 *                <array-item type="number">2</value>
 *              </array>
 *            </application>
 * `) => {'array':[1,2]}
 *
 * @param {string} xmlString
 * @returns
 */
function xml2json(xmlString) {
  if (xmlString) {
    const dom = new DOMParser().parseFromString(xmlString, 'text/xml');
    let error;
    if ((error = dom.querySelector('parsererror'))) {
      throw new Error('[xml2json] parse error' + error.innerHTML);
    }
    const res = node2json(dom.documentElement);
    if (res) delete res._attributes;
    return res;
  }
}
function toCamelCase(str) {
  return str.replace(/-(\w)/g, (_, letter) => letter.toUpperCase());
}
function formatValue(nodeValue, nodeType) {
  let result;
  if (nodeValue && '_text' in nodeValue) {
    result =
      {
        string: nodeValue._text,
        boolean: nodeValue._text === 'true',
        number: +nodeValue._text
      }[nodeValue?._attributes?.type] ?? nodeValue._text;
  } else {
    // 默认无值处理为空只字符串,如果指定为object,返回空对象
    const isEmptyObject =
      Object.keys(nodeValue).length === 0 ||
      (Object.keys(nodeValue).length === 1 && nodeValue._attributes?.type);
    result = isEmptyObject ? (nodeType === 'object' ? {} : '') : nodeValue;
  }
  // 只有一个attributes,那么删除
  if (result && result?._attributes?.type && Object.keys(result?._attributes).length == 1) {
    delete result._attributes;
  }

  return result;
}
function node2json(node) {
  const result = {};
  // 处理节点属性
  if (node.attributes && node.attributes.length > 0) {
    result._attributes = {};
    for (let attr of node.attributes) {
      result._attributes[attr.name] = attr.value;
    }
  }

  // 处理子节点
  if (node.hasChildNodes()) {
    const nodes = [];
    // 去掉换行符,避免注释插入位置错误
    for (let index = 0; index < node.childNodes.length; index++) {
      const ele = node.childNodes[index];
      // 对于text节点处理换行内容
      if (ele.nodeName === '#text') {
        if (ele.data.trim()) nodes.push(ele);
      } else {
        nodes.push(ele);
      }
    }

    for (let index = 0; index < nodes.length; index++) {
      const child = nodes[index];

      // 递归处理子节点
      if (child.nodeType === Node.ELEMENT_NODE) {
        const tagName = toCamelCase(child.tagName);
        // 处理数组
        if (child.getAttribute('type') === 'array') {
          result[tagName] = [];
          child.childNodes.forEach((child) => {
            if (child.nodeType === Node.ELEMENT_NODE) {
              result[tagName].push(formatValue(node2json(child), child.getAttribute('type')));
            }
          });
        } else {
          // 对仅字符节点直接处理为字符
          let childResult = formatValue(node2json(child), child.getAttribute('type'));

          // 如果节点只有文案没有属性,那么直接赋值
          if (tagName in result) {
            if (!Array.isArray(result[tagName])) result[tagName] = [result[tagName], childResult];
            else {
              result[tagName].push(childResult);
            }
          } else {
            result[tagName] = childResult;
          }
        }
      } else if (child.nodeType === Node.TEXT_NODE && child.textContent.trim()) {
        result['_text'] = child.textContent;
      }
    }
  }

  return result;
}

/**
 * @description 将值类型设置为type属性,在转json时会根据类型转换值
 * @example
 *  json2xml({
 *    "boolean":false,
 *    "string":"string",
 *    "array":[{"value":1},{"value":2}]
 *  }) =>
 *    <application>
 *        <boolean type="boolean">false</boolean>
 *        <string type="string">string</string>
 *        <array type="array">
 *          <array-item type="object">
 *            <value type="number">1</value>
 *            <value type="number">2</value>
 *          </array-item>
 *        </array>
 *    </application>
 * @param {object} json
 * @returns {string}
 *
 */
function json2xml(json, rootTagName = 'application') {
  if (!json) {
    return '';
  }

  function toKebabcase(str) {
    return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  }

  function buildTextNode(obj, key, parentNode) {
    const text = document.createTextNode(obj[key]);
    parentNode.appendChild(text);
  }
  function buildTextElement(obj, key, parentNode) {
    const node = document.createElement(toKebabcase(key));
    node.textContent = obj[key];
    // 设置值的类型,反序列化时候需要转为对应的对象
    node.setAttribute(
      'type',
      Object.prototype.toString
        .call(obj[key])
        .match(/\[object (\w+)\]/)[1]
        .toLowerCase()
    );
    parentNode.appendChild(node);
  }

  function buildItemXml(obj, key, parentNode) {
    if (Array.isArray(obj[key])) {
      const el = document.createElement(toKebabcase(key));
      el.setAttribute('type', 'array');
      parentNode.appendChild(el);

      obj[key].forEach((item, index) => {
        const arrayItem = document.createElement('array-item');
        arrayItem.setAttribute(
          'type',
          Object.prototype.toString
            .call(item)
            .match(/\[object (\w+)\]/)[1]
            .toLowerCase()
        );
        el.appendChild(arrayItem);
        buildItemXml(obj[key], index, arrayItem);
      });
    } else if (obj[key] && typeof obj[key] === 'object') {
      let el;
      if (typeof key === 'number') {
        el = parentNode;
      } else {
        el = document.createElement(toKebabcase(key));
        el.setAttribute('type', 'object');
        parentNode.appendChild(el);
      }
      buildXML(obj[key], el);
    } else if (key === '_text' || typeof key === 'number') {
      buildTextNode(obj, key, parentNode);
    } else {
      buildTextElement(obj, key, parentNode);
    }
  }
  function buildXML(obj, parentNode) {
    const keys = Object.keys(obj);
    const comments = obj._comments;

    keys.forEach((key) => {
      if (key === '_comments') return;
      if (key === '_attributes') {
        Object.keys(obj[key]).forEach((attrKey) => {
          parentNode.setAttribute(attrKey, obj[key][attrKey]);
        });
      } else {
        buildItemXml(obj, key, parentNode);
      }
    });

    if (comments) {
      const group = {};
      comments.forEach((comment) => {
        if (!group[comment.index]) {
          group[comment.index] = [];
        }
        group[comment.index].push(comment);
      });

      Object.keys(group)
        .sort((a, b) => +a - +b)
        .map((key) => group[key])
        .forEach((comments) => {
          comments.forEach((comment) => {
            const node = document.createComment(comment.text);
            parentNode.insertBefore(node, parentNode.childNodes[comment.index]);
          });
        });
    }
  }
  const doc = document.implementation.createDocument('', '', null);

  // 创建一个新的XML文档
  const root = document.createElement(rootTagName);
  doc.appendChild(root);
  buildXML(json, root);
  const serializer = new XMLSerializer();
  let xmlString = serializer.serializeToString(doc);
  xmlString = xmlString.replace(/<\?xml[^>]*\?>\s*/, '');
  return xmlFormat(xmlString, { indentation: '  ', collapseContent: true })
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&amp;/g, '&')
    .replace(/&quot;/g, '"')
    .replace(/&apos;/g, `'`);
}

export { xml2json, json2xml, xmlFormat };
